package com.wsl.HealthChallenge.controllers;


import java.io.IOException;
import java.net.URL;
import java.security.PrivateKey;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.google.gdata.client.Query;
import com.google.gdata.client.Query.CategoryFilter;
import com.google.gdata.client.health.HealthService;
import com.google.gdata.data.Category;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.health.ProfileEntry;
import com.google.gdata.data.health.ProfileFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.wsl.HealthChallenge.utils.DomXmlUtils;

/**
 * Used to request in a certain 
 * TODO(mark): In the first version all challenges will be open ended (The admin cannot enter an
 * end date currently). Therefore, we disabled the condition checking the endate in the calorie
 * requester. Enable it once we change that.
 * TODO(mark): Rename since it not only requests calories anymore.
 * 
 * @author mark
 */
public class GoogleHealthCalorieRequester {
    private HealthService healthService;
    private PrivateKey privateKey;
    private boolean verbose = false;

    /**
     * Constructor.
     * @param servletContext
     */
    public GoogleHealthCalorieRequester(ServletContext servletContext) {
        healthService = new HealthService("exampleCo-H9Sample-1.0");
        privateKey = GoogleHealthAuthUtils.loadPrivateKey(servletContext);
    }

    public int getTrackerValue(HttpServletRequest request, HttpServletResponse response,
            String sessionToken, String googleHealthTracker, Date startDate, Date endDate)
            throws IOException, ServiceException, AuthenticationException {
        int trackerValueSum = 0;
        healthService.setAuthSubToken(sessionToken, privateKey);
        Query query = new Query(new URL("https://www.google.com/health/feeds/profile/default"));
        CategoryFilter cf = new CategoryFilter(); 
        cf.addCategory(new Category("Labtest")); 
        query.addCategoryFilter(cf);
        query.addCustomParameter(new Query.CustomParameter("digest", "true"));
        query.addCustomParameter(new Query.CustomParameter("grouped", "true"));
        Set<ValueAndDate> uniqueEntries = new HashSet<ValueAndDate>();

        ProfileFeed feed = healthService.getFeed(query, ProfileFeed.class);

        if (feed.getEntries().size() > 0) {
            ProfileEntry digestEntry = feed.getEntries().get(0);
            String xml = 
                "<MyRoot>" +
                digestEntry.getContinuityOfCareRecord().getXmlBlob().getBlob() + 
                "</MyRoot>";

            Document doc = DomXmlUtils.parseDocumentFromString(xml);
            Element body = DomXmlUtils.getElement(doc.getDocumentElement(), "Body");
            Element results = DomXmlUtils.getElement(body, "Results");
            NodeList tests = results.getElementsByTagName("Test");

            DateTime startDateTime = new DateTime(startDate);
            // DateTime endDateTime = new DateTime(endDate);

            for (int i = 0; i < tests.getLength(); i++) {
                Element test = (Element) tests.item(i);
                Element descriptionEl = DomXmlUtils.getElementFromPath(test, "Description/Text");
                String description = descriptionEl.getTextContent();
                if (!description.equals(googleHealthTracker)) {
                    continue;
                }

                Element valueEl = DomXmlUtils.getElementFromPath(test, "TestResult/Value");
                String valueString = valueEl.getTextContent();

                DateTime collectionDate = getCollectionDate(test, response);
                if (collectionDate != null && collectionDate.getValue() >= startDateTime.getValue()
                        // TODO(mark): Enable this once we allow the admin to
                        // enter an enddate on the admin page.  
                        /* && collectionDate.getValue() < endDateTime.getValue()*/) {
                    int value = Integer.parseInt(valueString);
                    ValueAndDate newEntry = new ValueAndDate(collectionDate.getValue(), value);

                    if(!uniqueEntries.contains(newEntry)) {
                        uniqueEntries.add(newEntry);
                        trackerValueSum += value;
                    }
                }
            }
        }

        debugLogOutput(uniqueEntries);

        return trackerValueSum;
    }

    private void debugLogOutput(Set<ValueAndDate> uniqueEntries) {
        if (verbose) {
            StringBuilder builder = new StringBuilder();
            for (ValueAndDate entry : uniqueEntries) {
                builder.append(entry.collectionDate);
                builder.append(": ");
                builder.append(entry.value);
                builder.append("\n");
            }
            UpdateScoreController.log.log(Level.SEVERE, "verbose:#uniqe entries:" + uniqueEntries.size());
            UpdateScoreController.log.log(Level.SEVERE, "verbose:uniqe entries:" + builder.toString());
        }
    }

    /**
     * Reads the collection date from the given element.
     * @param test
     * @param response
     * @return
     * @throws DOMException
     * @throws IOException
     */
    private DateTime getCollectionDate(Element test, HttpServletResponse response)
            throws DOMException, IOException {
        String collectionDateString = getExactDateTimeString(test);
        DateTime collectionDateTime = parseDateTime(collectionDateString);
        if (collectionDateTime == null && collectionDateString.length() > 2)  {
            int insertIndex = collectionDateString.length() - 2;
            String repairedDateString = collectionDateString.substring(0, insertIndex) + ":" + 
                collectionDateString.substring(insertIndex);
            collectionDateTime = parseDateTime(repairedDateString);
        }
        if(collectionDateTime == null) {
            collectionDateTime = parseDate(collectionDateString);
        }

        return collectionDateTime;
    }

    /**
     * 
     * @param test
     * @return
     */
    private String getExactDateTimeString(Element test) {
        Element exactDateTime = DomXmlUtils.getElement(test, "ExactDateTime");
        if (exactDateTime == null) {
            return null;
        }
        return exactDateTime.getTextContent();
    }

    /**
     * Parses the collection date and time from the given string.
     * @param collectionDate
     * @return
     */
    private DateTime parseDateTime(String collectionDate) {
        try {
            return DateTime.parseDateTime(collectionDate);
        } catch (DOMException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Parses the collection date from the given string.
     * @param collectionDate
     * @return
     */
    private DateTime parseDate(String collectionDate) {
        try {
            return DateTime.parseDate(collectionDate);
        } catch (DOMException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void setVerbose(boolean enabled) {
        verbose  = enabled;
    }
}